/*
 * IRC<->JSON-P interface
 * (c) 2009 sd@pilsfree.net
 *
 * The API is as follows:
 *
 * request script: src=http://host/json/IDK/SEQ/<urlencodedcontent>
 * IDK - valid session key (may be empty), if none is provided
 *	callback: irc_setup(idk)
 *	you should mark down IDK and use it for subsequent calls
 * SEQ - return data since SEQ (seq 0 is start of stream since irc_reset)
 *	waiting at top most known SEQ makes the request call for more data to come.
 *	callback: irc_update(ts,seq,isecho,"datafromirc")
 *		ts - timestamp when the data were received
 *		seq - sequence of these data (see SEQ above)
 *		isecho - this is just an echo of data posted TO irc
 *	note that irc_update may be called multiple times (for each seq since SEQ)
 */

#include "ircid.h"
static char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/javascript\r\nTransfer-Encoding: chunked\r\n\r\n";

/*************************************************************************
 * dealing with irc client-side 
 *************************************************************************/

/* global client list */
client_info	*clients, *clients_free;
int	nclients = 0;

static char idfmt[8];

static	void try_satisfy(request_info *ri);

static	void	prealloc_clients(int n)
{
	clients = realloc(clients, (n + nclients) * sizeof(clients[0]));
	memset(clients + nclients, 0, sizeof(clients[0]) * n);
	n += nclients;
	for (;nclients < n;nclients++) {
		clients[nclients].next_free = clients_free;
		clients_free = &clients[nclients];
	}
}

/* find client belonging to given idk.
 * if idk cannot be found or is NULL, new client should be allocated.
 */
static	void *client_find(request_info *ri, const char *idk)
{
	unsigned i;
	client_info *c;

	DEBUG("find_client %s", idk);
	if (!idk)
		goto invalid;

	if (sscanf(idk, idfmt, &i) != 1)
		goto invalid;

	/* too high */
	if (i >= nclients)
		goto invalid;

	c = clients + i;

	/* it's not active */
	if (!c->active)
		goto invalid;

	/* invalid idk match */
	if (memcmp(clients[i].idk, idk, IDK_LEN))
		goto invalid;

	ri->c = c;
	/* ok, looks like our hit */
	return c;
invalid:;
	/* register new client */
	if (!clients_free)
		prealloc_clients(1);
	c = clients_free;
	clients_free = c->next_free;
	memset(c, 0, sizeof(*c));
	c->reqs = ri;
	c->active = 1;
	ri->c = c;
	sprintf(c->idk, idfmt, c-clients);
	hex_random_bytes(c->idk + ID_LEN, K_LEN);
	return c;
}

/* append a string to the receive queue. either be it from irc or log user
   what sent to us (isecho=user */
#define TRAILER "');\n\r\n"
#define HEADER "00000000\r\n"
static	void rq_append(client_info *cli, int isecho, char *buf, int len)
{
	DEBUG("rq_append(%d)", len);
	int nume = count_escapes(buf, len);
	char bufb[128];
	int l;
	struct rentry *rq;
#define RQ_PREALLOC 8
	DEBUG("# of escapes=%d", nume);
	if ((cli->rqend - cli->rqpos) % RQ_PREALLOC == 0)
		cli->rq = realloc(cli->rq, (cli->rqend - cli->rqpos + RQ_PREALLOC) * sizeof(rq));
	l = sprintf(bufb, "%sirc_update(%d,%d,%d,'", HEADER, (int)ev_now(cli->c.loop), isecho, cli->rqend);
	zealloc(rq, l+nume+len+strlen(TRAILER)+1);
	memcpy(rq->data, bufb, l);
	escape_buffer(rq->data + l, buf, len);
	strcpy(rq->data + l + len + nume, TRAILER);
	sprintf(rq->data, "%08x", l + len + nume + strlen(TRAILER) - strlen(HEADER) - 2); /* provide chunk size */
	rq->data[8] = '\r';
	rq->next = NULL;

	cli->rq[cli->rqend++] = rq;
}
static void irc_write_done_callback(ebb_connection *c, void *buf);

static void execute_pending(client_info *cli)
{
	/* this will gradually process all pendings */
	if (cli->pending) {
		request_info *ri = cli->pending;
		cli->pending = ri->next;

		/* add us to known requests */
		ri->next = cli->reqs;
		cli->reqs = ri;

		/* this must succeed */
		assert(ebb_connection_write(&ri->c->c, ri->rbuf + ri->rlen, strlen(ri->rbuf + ri->rlen), irc_write_done_callback));
	}
}

/* data were posted to irc socket. check if more is coming
   and try to satisfy http readers as well */
static void irc_write_done_callback(ebb_connection *c, void *buf)
{
	client_info *cli = c->data;
	request_info *ri;

	execute_pending(cli);

	/* try to satisfy all the requests now */
	for (ri = cli->reqs; ri; ri = ri->next)
		try_satisfy(ri);
}

/* data coming from irc */
static	int irc_on_data(ebb_connection *conn,char *buf, int len)
{
	char *s;
	char *nbuf;
	int nlen, ilen;
	client_info *cli = conn->data;
	request_info *ri;

	if (!len) return EBB_AGAIN;

	/* memrchr */
	for (s = buf + len-1; *s != '\n' && s > buf; s--);

	/* no terminator */
	if (s == buf && *s != '\n') {
		/* this is violation of IRC protocol */
		if (cli->ibuflen + len > IBUF)
			goto invalid;
		memcpy(cli->ibuf + cli->ibuflen, buf, len);
		cli->ibuflen += len;
		return EBB_AGAIN;
	}

	/* found terminator */
	ilen = (s+1)-buf;
	nlen = cli->ibuflen + ilen;
	assert(nlen>0);
	nbuf = alloca(nlen);
	memcpy(nbuf, cli->ibuf, cli->ibuflen);
	memcpy(nbuf + cli->ibuflen, buf, ilen);
	rq_append(cli, 0, nbuf, nlen);

	/* remember the remainder */
	cli->ibuflen = len-ilen;
	if (cli->ibuflen > IBUF)
		goto invalid;
	memcpy(cli->ibuf, s+1, cli->ibuflen);

	/* try to satisfy all the requests now */
	for (ri = cli->reqs; ri; ri = ri->next)
		try_satisfy(ri);
	return EBB_AGAIN;
invalid:;
	ebb_connection_schedule_close(conn);
	return EBB_STOP;
}

/* snipe all requests and pendings and release structure to the pool */
static	void irc_on_close(ebb_connection *conn)
{
	client_info *cli = conn->data;
	request_info *ri;
	int i;

	/* kill anyone still interested */
	for (ri = cli->reqs; ri; ri = ri->next)
		ebb_connection_schedule_close(ri->conn);
	for (ri = cli->pending; ri; ri = ri->next)
		ebb_connection_schedule_close(ri->conn);

	/* free the queues */
	for (i = 0; i < (cli->rqend-cli->rqpos); i++)
		zfree(cli->rq[i]);
	free(cli->rq);

	/* release to the pool */
	cli->active = 0;
	cli->next_free = clients_free;
	clients_free = cli;
}

/* start up new irc client connection */
static	void irc_client(struct ev_loop *loop, client_info *cli, const char *host, int port)
{
	DEBUG("starting irc client");
	ebb_connection_init(&cli->c);
	cli->c.data = cli;
	cli->c.on_data = irc_on_data;
	cli->c.on_close = irc_on_close;
	cli->c.loop = loop;
	ebb_tcp_client(&cli->c, host, port);
}


/*************************************************************************
 * dealing with http server-side 
 *************************************************************************/
/* finish the request and prepare the connection for new one */

/* remove 'entry' from given list */
static void remove_from_reqlist(request_info **listp, request_info *entry)
{
	request_info *ri, *rinext;
	for (ri = *listp; ri; ri = rinext) {
		rinext = ri->next;
		if (ri == entry) {
			*listp = rinext;
			return;
		}
		listp = &ri->next;
	}
	
}

/* remove association between request and irc client */
static void destroy_request(request_info *ri)
{
	if (ri->c) {
		remove_from_reqlist(&ri->c->reqs, ri);
		remove_from_reqlist(&ri->c->pending, ri);
	}
	ri->conn->after_write_cb  = NULL;
	ri->c = NULL;
}

/* prepare parser for new request or close the connection */
static	void finish_request(ebb_connection *c, void *d)
{
	ebb_request *r = struct_off(c, sizeof(*c) + sizeof(*c->parser));
	request_info *ri = struct_off(r, sizeof(*r));

	if (ebb_request_should_keep_alive(r))
		/* start processing more */
		ev_io_start(c->loop, &c->read_watcher);
	else
		ebb_connection_schedule_close(c);
	destroy_request(ri);
}

/* printf javascript response */
static	void r_printf(request_info *ri, int skew, const char *fmt, ...)
{
	char buf[RBUF];
	va_list ap;
	int l;

	va_start(ap, fmt);
	l = vsprintf(buf, fmt, ap);
	ri->rlen = sprintf(ri->rbuf,
		"HTTP/1.1 200 OK\r\nContent-Type: text/javascript\r\nContent-Length: %d\r\n\r\n%s",
				l + skew, buf);

	ebb_connection_write(ri->conn, ri->rbuf, ri->rlen, finish_request);
}

/* start writing the next chunk */
static	void response_callback(ebb_connection *c, void *d)
{
	unsigned sendlen;
	char *data;

	ebb_request *r = struct_off(c, sizeof(*c) + sizeof(*c->parser));
	request_info *ri = struct_off(r, sizeof(*r));

	/* Dont forward the queue for the first time */
//	if (d != response)
//		ri->rq = ri->rq->next;

	if (ri->rseq >= ri->c->rqend) {
		/* last one, finishing! */
		ebb_connection_write(ri->conn, "0\r\n\r\n", 5, finish_request);
		return;
	}

	data = ri->c->rq[ri->rseq++]->data;
	assert(sscanf(data, "%08x", &sendlen) == 1);
	DEBUG("SENDLEN=%d %s\n", sendlen + 12, data);
	DEBUG("to_write=%p", ri->conn->to_write);
	assert(ebb_connection_write(ri->conn, data, sendlen + 12, response_callback));
}

/* we'll try to come up response for the http request.
 * first check we don't have (ri->sendbuf) which means
 * we are still writing to irc_client socket.
 */
static	void try_satisfy(request_info *ri)
{
	client_info *c = ri->c;

	/* already responding */
	if (ri->conn->after_write_cb)
		return;

	/* find some then! */
	if (ri->rseq < c->rqpos)
		ri->rseq = c->rqpos;
	/* wait some more */
	if (ri->rseq >= c->rqend)
		return;
	DEBUG("writing response @ %p", response);
	ebb_connection_write(ri->conn, response, strlen(response), response_callback);
}

/* the request is now set-up. decide what to do next. */
static void http_request_complete(ebb_request *req)
{
	request_info *ri = req->data;
	char *parts[3];
	int i,l;

	if (strncmp(ri->rbuf, "/json/", 6))
		goto invalid;

	/* path must be 3 elements */
	for (i=0; i < 3; i++)
		/* invalid request */
		if (!(parts[i] = strtok(i?NULL:(ri->rbuf+6),"/")))
			goto invalid;

	/* stop reading further events from http. instead continue with writes in irc_write_done_callback */
	ev_io_stop(ri->conn->loop, &ri->conn->read_watcher);


	if (!client_find(ri, parts[0]))
		goto invalid;

#if 0
{
		struct SOCKADDR_IN sin;
		socklen_t sinlen = sizeof(sin);
		/* we don't know the client's whereabouts as of yet; start auth */
		if (getpeername(ri->conn, &sin, sinlen) < 0)
			goto invalid;
		auth_start(ri->conn->loop, &ri->conn->server->sockaddr, &sin);
//		r_printf(ri, 0, "irc_setup(%s);", ri->c->idk);
		return;
	}
#endif

	/* we don't have client connection as of yet, make one */
	if (!ri->c->connected) {
		char **argv = ri->conn->server->data;
		DEBUG("will create the client!");
		irc_client(ri->conn->loop, ri->c, argv[1], atol(argv[2]));
		ri->c->connected = 1;
	}

	ri->rseq = atol(parts[1]);

	/* first, we want to update the client stream. */
	url_decode(parts[2]);
	l = strlen(parts[2]);

	/* echo back */
	rq_append(ri->c, 1, parts[2], l);

	/* this is saved for pending */
	ri->rlen = parts[4] - ri->rbuf;

	/* write to irc socket, or attach to pending queue */
	if (!ebb_connection_write(&ri->c->c, parts[2], strlen(parts[2]), irc_write_done_callback)) {
		/* failed to write - add to pending */
		ri->next = ri->c->pending;
		ri->c->pending = ri;
	} else {
		/* will be notified - known request */
		ri->next = ri->c->reqs;
		ri->c->reqs = ri;
	}

	return;
invalid:;
	ebb_connection_schedule_close(ri->conn);
}

/* the path of a request is made known */
void	request_on_path(ebb_request *r, const char *at, size_t len)
{
	DEBUG("on_path %d", len);
	request_info *ri = r->data;
	if (ri->rlen + len >= RBUF) {
		/* line too long */
		ebb_connection_schedule_close(ri->conn);
		return;
	}
	memcpy(ri->rbuf + ri->rlen, at, len);
	ri->rlen += len;
	ri->rbuf[ri->rlen] = 0;
}

/* new http request coming */
ebb_request *http_request(void *d)
{
	DEBUG("got new request %p", d);
	ebb_connection *c = d;
	ebb_request *r = struct_off(c, sizeof(*c) + sizeof(*c->parser));
	request_info *ri = struct_off(r, sizeof(*r));
	ebb_request_init(r);
	DEBUG("to memset: %d", (((char *) ri) + sizeof(*ri)) - (char*)d);
	memset(ri, 0, sizeof(*ri));
	ri->conn = d;
	r->data = ri;
	r->on_path = request_on_path;
	r->on_complete = http_request_complete;
	/* just return correct ptr */
	return r;
}


/* close the http connection */
static void http_close(ebb_connection *c)
{
	ebb_request *r = struct_off(c, sizeof(*c) + sizeof(*c->parser));
	request_info *ri = struct_off(r, sizeof(*r));
	destroy_request(ri);
	zfree(c);
}

/* new connection to http socket */
ebb_connection *http_new(ebb_server *server, SIN *addr)
{
	ebb_connection *c;
	DEBUG("got new connection");
	/* combine the parser and request inside connection.
         * we are NOT supporting request pipelining */
	zealloc(c, sizeof(*c->parser) + sizeof(*c->parser->current_request) + sizeof(request_info));
	ebb_connection_init(c);
	c->parser = struct_off(c, sizeof(*c));
	ebb_request_parser_init(c->parser);
	c->parser->data = c;
	c->parser->new_request = http_request;
	c->on_close = http_close;
	return c;
}

ebb_server	*http_init(struct ev_loop *loop, int argc, char *argv[])
{
	ebb_server *server;


	sprintf(idfmt, "%%0%dx", ID_LEN);
	zalloc(server);
	server->data = argv;
	ebb_server_init(server, loop);
	
	server->data = argv;
	server->new_connection = http_new;

	if (argc == 5)
		ebb_server_set_secure(server, argv[3], argv[4]);
	ebb_tcp_server(server, NULL, atol(argv[0]));
	return server;
}

